package com.sc.nft.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.lang.WeightRandom;
import cn.hutool.core.lang.WeightRandom.WeightObj;
import cn.hutool.core.util.DesensitizedUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.sc.nft.dao.MallDrawRecordDao;
import com.sc.nft.entity.EquityProps;
import com.sc.nft.entity.MallDrawActivityBase;
import com.sc.nft.entity.MallDrawActivityPrizePool;
import com.sc.nft.entity.MallDrawActivityPrizePoolDetail;
import com.sc.nft.entity.MallDrawActivityProp;
import com.sc.nft.entity.MallDrawRecord;
import com.sc.nft.entity.UserInfo;
import com.sc.nft.entity.dto.draw.MallDrawPrizeDTO;
import com.sc.nft.entity.dto.draw.activity.AutoReplenishmentDTO;
import com.sc.nft.entity.dto.draw.activity.MallDrawActivityRecordDTO;
import com.sc.nft.entity.vo.UserHoldPropsCountVO;
import com.sc.nft.entity.vo.draw.MallDrawActivityVO;
import com.sc.nft.entity.vo.draw.MallDrawPrizeNoticeVO;
import com.sc.nft.entity.vo.draw.MallDrawPrizePoolVO;
import com.sc.nft.entity.vo.draw.MallDrawPrizeResultVO;
import com.sc.nft.entity.vo.draw.MallDrawPrizeVO;
import com.sc.nft.entity.vo.draw.MallSimpleDrawPrizePoolVO;
import com.sc.nft.entity.vo.draw.MallSimplePropVO;
import com.sc.nft.entity.vo.draw.MallUserDrawRecodVO;
import com.sc.nft.entity.vo.draw.MallUserDrawRecordDetailVO;
import com.sc.nft.entity.vo.draw.MallUserPrizeDetailVO;
import com.sc.nft.entity.vo.draw.MallUserPrizeVO;
import com.sc.nft.entity.vo.draw.activity.MallDrawActivityRecordDetailVO;
import com.sc.nft.entity.vo.draw.activity.MallDrawActivityRecordVO;
import com.sc.nft.enums.AssetChangeLogEnum;
import com.sc.nft.enums.CoinCategoryEnum;
import com.sc.nft.enums.UserEquityPropsGetTypeEnum;
import com.sc.nft.enums.UserEquityPropsUseTypeEnum;
import com.sc.nft.enums.draw.MallDrawStatus;
import com.sc.nft.enums.draw.activity.AutoReplenishmentEnum;
import com.sc.nft.enums.draw.activity.PropTypeEnum;
import com.sc.nft.exception.GlobalRunTimeException;
import com.sc.nft.service.EquityPropsService;
import com.sc.nft.service.MallDrawActivityBaseService;
import com.sc.nft.service.MallDrawActivityPrizePoolDetailService;
import com.sc.nft.service.MallDrawActivityPrizePoolService;
import com.sc.nft.service.MallDrawActivityPropService;
import com.sc.nft.service.MallDrawRecordService;
import com.sc.nft.service.UserAssetsService;
import com.sc.nft.service.UserEquityPropsService;
import com.sc.nft.service.UserInfoService;
import com.sc.nft.sup.PageRequest;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Collectors;

@Service
@Slf4j
public class MallDrawRecordServiceImpl implements MallDrawRecordService {

    private static final BigDecimal ONE_HUNDRED = new BigDecimal(100);

    private static final String TITLE_FORMAT = "第%s轮抽奖时间";

    private static final String WIN_FORMAT = "中奖【%s*%s】";

    private static final String NOT_WIN_DESC = "未中奖【清空奖池内奖品】";

    private static final String EXPIRED_DESC = "奖池失效【清空奖池内奖品】";

    private static final String PRIZE_DESC = "【%s*%s】";

    private static final String PRIZE_NOTICE_KEY = "mall_prize_notice_";

    @Autowired
    private UserEquityPropsService userEquityPropsService;

    @Autowired
    private UserAssetsService userAssetsService;

    @Autowired
    private MallDrawRecordDao mallDrawRecordDao;

    @Autowired
    private EquityPropsService equityPropsService;

    @Autowired
    private MallDrawActivityPrizePoolDetailService mallDrawActivityPrizePoolDetailService;

    @Autowired
    private MallDrawActivityBaseService mallDrawActivityBaseService;

    @Autowired
    private MallDrawActivityPrizePoolService mallDrawActivityPrizePoolService;

    @Autowired
    private UserInfoService userInfoService;

    @Autowired
    private MallDrawActivityPropService mallDrawActivityPropService;

    @Autowired
    private RedisTemplate redisTemplate;

    @Override
    public IPage<MallDrawActivityVO> listDrawActivity(PageRequest pageRequest) {
        Page<MallDrawActivityBase> pageResult = mallDrawActivityBaseService.listEnabledActivity(pageRequest.toMp());
        if (CollectionUtil.isEmpty(pageResult.getRecords())) {
            return new Page<>(pageResult.getCurrent(), pageResult.getSize(), pageResult.getTotal());
        }
        Date now = new Date();
        IPage<MallDrawActivityVO> result = pageResult.convert(activity -> {
            MallDrawActivityVO activityVO = new MallDrawActivityVO();
            activityVO.setId(activity.getId());
            activityVO.setName(activity.getName());
            activityVO.setCoverImageUrl(activity.getCoverImageUrl());
            // 活动状态 0-待开始 1-进行中 2-已结束
            if (now.before(activity.getStartTime())) {
                activityVO.setStatus(0);
            } else if (now.after(activity.getEndTime())) {
                activityVO.setStatus(2);
            } else {
                activityVO.setStatus(1);
            }
            return activityVO;
        });
        return result;
    }

    @Override
    public IPage<MallDrawActivityRecordVO> adminListUserPrizeRecord(MallDrawActivityRecordDTO mallDrawActivityRecordDTO) {
        Long userId = null;
        if (StringUtils.isNotEmpty(mallDrawActivityRecordDTO.getPhoneNumber())) {
            UserInfo userInfo = userInfoService.getByUserTel(mallDrawActivityRecordDTO.getPhoneNumber());
            if (userInfo == null) {
                return null;
            }
            userId = userInfo.getId();
        }

        Page<MallDrawRecord> pageResult = mallDrawRecordDao.listUserDrawRecord(mallDrawActivityRecordDTO.getId(), mallDrawActivityRecordDTO.getPrizeName(), mallDrawActivityRecordDTO.getStartTime(), mallDrawActivityRecordDTO.getEndTime(), userId, mallDrawActivityRecordDTO.toMp());
        if (pageResult == null || CollectionUtil.isEmpty(pageResult.getRecords())) {
            return null;
        }
        Set<Long> activityIds = pageResult.getRecords().stream().map(MallDrawRecord::getActivityId)
                .collect(Collectors.toSet());
        List<Long> userIds = pageResult.getRecords().stream().map(MallDrawRecord::getUserId)
                .collect(Collectors.toList());

        List<UserInfo> userInfoList = userInfoService.getByIdList(userIds);
        Map<Long, UserInfo> userMap = userInfoList.stream().collect(Collectors.toMap(UserInfo::getId, user -> user, (key1, key2) -> key2));

        List<MallDrawActivityBase> actList = mallDrawActivityBaseService.listByIds(new ArrayList<>(activityIds));
        Map<Long, Date> actMap = actList.stream()
                .collect(Collectors.toMap(MallDrawActivityBase::getId, MallDrawActivityBase::getEndTime));
        List<MallDrawActivityProp> drawActivityPropList = mallDrawActivityPropService.listByActivityId(mallDrawActivityRecordDTO.getId());
        Integer propCount = 0;
        for (MallDrawActivityProp mallDrawActivityProp : drawActivityPropList) {
            propCount += mallDrawActivityProp.getPropCount();
        }
        Date now = new Date();
        Integer finalPropCount = propCount;
        IPage<MallDrawActivityRecordVO> result = pageResult.convert(drawRecord -> {
            MallDrawPrizeDTO mallDrawPrizeDTO = JSON.parseObject(drawRecord.getPrizeInfo(), MallDrawPrizeDTO.class);
            MallDrawActivityRecordVO vo = new MallDrawActivityRecordVO();
            UserInfo userInfo1 = userMap.get(drawRecord.getUserId());
            if (userInfo1 != null) {
                vo.setNickName(userInfo1.getNickName());
                vo.setPhoneNumber(userInfo1.getUserTel());
            }
            vo.setId(drawRecord.getId());
            if (mallDrawPrizeDTO != null) {
                vo.setPrizeName(mallDrawPrizeDTO.getPropName());
                vo.setPrizeCount(mallDrawPrizeDTO.getPrizeCount());
            }
            vo.setCreateTime(drawRecord.getCreateTime());
            if (drawRecord.getDrawStatus() == MallDrawStatus.ACCEPTED.getCode()) {
                vo.setModifyTime(drawRecord.getModifyTime());
            }
            vo.setDrawStatus(drawRecord.getDrawStatus());
            Date endTime = actMap.get(drawRecord.getActivityId());
            // 未领取且已过期
            if (MallDrawStatus.PROCESSING.getCode() == drawRecord.getDrawStatus() && now.after(endTime)) {
                vo.setDrawStatus(MallDrawStatus.EXPIRED.getCode());
            }

            List<MallDrawRecord> mallDrawRecordList = mallDrawRecordDao.listByUserIdAndParentId(drawRecord.getUserId(), drawRecord.getActivityId(), drawRecord.getId());
            Integer recordSize = 1;
            if (CollectionUtil.isNotEmpty(mallDrawRecordList)) {
                List<MallDrawActivityRecordDetailVO> mallDrawActivityRecordDetailVOList = new ArrayList<>();
                for (MallDrawRecord mallDrawRecord : mallDrawRecordList) {
                    MallDrawActivityRecordDetailVO mallDrawActivityRecordDetailVO = new MallDrawActivityRecordDetailVO();
                    mallDrawActivityRecordDetailVO.setDrawStatus(mallDrawRecord.getDrawStatus());
                    mallDrawActivityRecordDetailVO.setPoolIndex(String.valueOf(mallDrawRecord.getPoolIndex()));
                    mallDrawActivityRecordDetailVO.setCreateTime(mallDrawRecord.getCreateTime());
                    mallDrawActivityRecordDetailVO.setPrizeInfoDTO(JSON.parseObject(mallDrawRecord.getPrizeInfo(), MallDrawPrizeDTO.class));
                    mallDrawActivityRecordDetailVOList.add(mallDrawActivityRecordDetailVO);
                }
                vo.setMallDrawActivityRecordDetailVOList(mallDrawActivityRecordDetailVOList);
                recordSize += mallDrawRecordList.size();
            }
            vo.setPropCount(finalPropCount * recordSize);
            return vo;
        });
        return result;
    }

    @Override
    public IPage<MallUserDrawRecodVO> listUserPrizeRecord(Long userId, PageRequest pageRequest) {
        Page<MallDrawRecord> pageResult = mallDrawRecordDao.listUserDrawRecord(userId, pageRequest.toMp());
        if (CollectionUtil.isEmpty(pageResult.getRecords())) {
            return new Page<>(pageResult.getCurrent(), pageResult.getSize(), pageResult.getTotal());
        }
        Set<Long> activityIds = pageResult.getRecords().stream().map(MallDrawRecord::getActivityId)
                .collect(Collectors.toSet());
        List<MallDrawActivityBase> actList = mallDrawActivityBaseService.listByIds(new ArrayList<>(activityIds));
        Map<Long, Date> actMap = actList.stream()
                .collect(Collectors.toMap(MallDrawActivityBase::getId, MallDrawActivityBase::getEndTime));
        Date now = new Date();
        IPage<MallUserDrawRecodVO> result = pageResult.convert(drawRecord -> {
            MallUserDrawRecodVO vo = new MallUserDrawRecodVO();
            vo.setId(drawRecord.getId());
            vo.setActivityId(drawRecord.getActivityId());
            vo.setCreateTime(drawRecord.getCreateTime());
            vo.setDrawStatus(drawRecord.getDrawStatus());
            Date endTime = actMap.get(drawRecord.getActivityId());
            // 未领取且已过期
            if (MallDrawStatus.PROCESSING.getCode() == drawRecord.getDrawStatus() && now.after(endTime)) {
                vo.setDrawStatus(MallDrawStatus.EXPIRED.getCode());
            }
            return vo;
        });
        return result;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public MallDrawPrizeResultVO drawPrize(UserInfo userInfo, Long prizePoolId, Long activityId) {
        Date now = new Date();
        MallDrawActivityBase drawActivity = mallDrawActivityBaseService.getById(activityId);
        if (drawActivity == null) {
            throw new GlobalRunTimeException("活动不存在");
        } else if (drawActivity.getStartTime().after(now)) {
            throw new GlobalRunTimeException("活动暂未开始");
        } else if (drawActivity.getEndTime().before(now)) {
            throw new GlobalRunTimeException("活动已结束");
        }

        MallDrawActivityPrizePool prizePool = mallDrawActivityPrizePoolService.getById(prizePoolId);
        if (prizePool == null || !prizePool.getActivityId().equals(activityId)) {
            throw new GlobalRunTimeException("活动不存在");
        }

        Long parentId = 0L;
        // 之前中奖记录
        List<MallDrawRecord> drawRecords = mallDrawRecordDao.listByUserIdActIdAndStatus(userInfo.getId(), activityId,
                MallDrawStatus.PROCESSING);
        if (CollectionUtil.isNotEmpty(drawRecords)) {
            MallDrawRecord currentDrawRecord = drawRecords.get(drawRecords.size() - 1);
            if (prizePool.getPoolIndex() <= currentDrawRecord.getPoolIndex()) {
                throw new GlobalRunTimeException("本轮抽奖已参与过");
            }
            if ((currentDrawRecord.getPoolIndex() + 1) != prizePool.getPoolIndex()) {
                throw new GlobalRunTimeException("抽中上一轮奖池奖品才能解锁本轮抽奖");
            }
            parentId = drawRecords.get(0).getId();
        } else {
            if (prizePool.getPoolIndex() != 1) {
                throw new GlobalRunTimeException("抽中上一轮奖池奖品才能解锁本轮抽奖");
            }
        }

        List<MallDrawActivityProp> actProps = mallDrawActivityPropService.listByActivityId(activityId);
        List<Long> propIds = actProps.stream().map(MallDrawActivityProp::getPropId).collect(Collectors.toList());
        List<UserHoldPropsCountVO> propsHoldCountList = userEquityPropsService.getUserPropsHoldCount(userInfo.getId(), propIds);
        Map<Long, Integer> holdCountMap = propsHoldCountList.stream().collect(Collectors
                .toMap(UserHoldPropsCountVO::getEquityPropsId, UserHoldPropsCountVO::getHoldCount, (k1, k2) -> k1));
        boolean hasEnoughProps = actProps.stream().allMatch(actProp -> holdCountMap.get(actProp.getPropId()) != null
                && holdCountMap.get(actProp.getPropId()) >= actProp.getPropCount());
        if (!hasEnoughProps) {
            throw new GlobalRunTimeException("道具不足，无法参与抽奖");
        }
        // 消耗道具
        for (MallDrawActivityProp prop : actProps) {
            subVoucher(userInfo.getId(), prop.getPropId(), prop.getPropCount(), drawActivity.getId());
        }

        List<MallDrawActivityPrizePoolDetail> prizeList = mallDrawActivityPrizePoolDetailService
                .listByPrizePoolId(prizePoolId);
        if (CollectionUtil.isEmpty(prizeList)) {
            throw new GlobalRunTimeException("奖池为空，无法参与抽奖");
        }
        // 计算总权重，仅包括剩余库存大于 0 的奖品或 PropTypeEnum.NONE 类型的奖品
        BigDecimal totalProbability = prizeList.stream()
                .filter(prize -> (prize.getRemainingPrizeCount() != null && prize.getRemainingPrizeCount() > 0)
                        || PropTypeEnum.NONE.equals(prize.getPropType())) // 库存大于 0 或类型为 NONE
                .map(MallDrawActivityPrizePoolDetail::getProbability)
                .filter(Objects::nonNull) // 排除 null 的权重值
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        List<WeightObj<MallDrawActivityPrizePoolDetail>> weightList = new ArrayList<>();
        prizeList.forEach(prize -> {
            if ((prize.getRemainingPrizeCount() != null && prize.getRemainingPrizeCount() >= prize.getPrizeCount())
                    || PropTypeEnum.NONE.equals(prize.getPropType())) {
                weightList.add(new WeightObj<>(prize, Convert.toDouble(prize.getProbability())));
            }
        });
        WeightRandom<MallDrawActivityPrizePoolDetail> weightRandom = RandomUtil.weightRandom(weightList);
        MallDrawActivityPrizePoolDetail prizeDetail = weightRandom.next();

        boolean winPrize = false;
        if (prizeDetail != null) {
            if (!PropTypeEnum.NONE.equals(prizeDetail.getPropType())) {
                // 扣减库存
                boolean deductResult = mallDrawActivityPrizePoolDetailService.changePrizeCount(prizeDetail.getId(),
                        prizeDetail.getPrizeCount(), true);
                // 扣减失败则未中奖
                if (deductResult) {
                    winPrize = true;
                    //如果是最后一份，开始自动补库
                    if ((prizeDetail.getRemainingPrizeCount() != null && prizeDetail.getRemainingPrizeCount().equals(prizeDetail.getPrizeCount()))) {
                        //查补库配置
                        Boolean autoReplenishmentEnabled = prizeDetail.getAutoReplenishmentEnabled();
                        if (autoReplenishmentEnabled) {
                            AutoReplenishmentDTO autoReplenishmentDTO = JSON.parseObject(prizeDetail.getAutoReplenishmentJson(), AutoReplenishmentDTO.class);
                            if (AutoReplenishmentEnum.AUTO_REPLENISH_WHEN_ZERO == autoReplenishmentDTO.getAutoReplenishmentEnum()) {
                                // 当库存为 0 时，自动补充 N 份道具
                                int replenishCount = autoReplenishmentDTO.getTotalPrizeCount();
                                mallDrawActivityPrizePoolDetailService.replenishPrizeCount(prizeDetail.getId(), replenishCount);
                            } else {
                                // 当本奖池总权重小于X时，自动补充N份道具
                                int replenishCount = autoReplenishmentDTO.getTotalPrizeCount();
                                BigDecimal totalProbabilityConfig = autoReplenishmentDTO.getTotalProbability();
                                if ((totalProbability.subtract(prizeDetail.getProbability())).compareTo(totalProbabilityConfig) < 0) {
                                    mallDrawActivityPrizePoolDetailService.replenishPrizeCount(prizeDetail.getId(), replenishCount);
                                }
                            }
                        }
                    }
                }
            }
        }

        // 奖池轮数
        int prizePoolCount = mallDrawActivityPrizePoolService.countByActivityId(activityId);
        // 单轮奖池则自动领取奖品
        boolean singlePool = prizePoolCount == 1;

        MallDrawRecord drawRecord = new MallDrawRecord();
        if (prizeDetail != null) {
            MallDrawPrizeDTO prizeDTO = new MallDrawPrizeDTO();
            prizeDTO.setPrizeCount(prizeDetail.getPrizeCount());
            prizeDTO.setPropId(prizeDetail.getPropId());
            prizeDTO.setPropImage(prizeDetail.getPropImg());
            prizeDTO.setPropName(prizeDetail.getPropName());
            prizeDTO.setPropType(prizeDetail.getPropType().getCode());
            drawRecord.setPrizeInfo(JSON.toJSONString(prizeDTO));
        } else {
            drawRecord.setPrizeInfo("");
        }
        drawRecord.setActivityId(activityId);
        MallDrawStatus drawStatus = MallDrawStatus.NOT_WIN;
        if (winPrize) {
            // 单论奖池自动发奖
            if (singlePool) {
                drawStatus = MallDrawStatus.ACCEPTED;
            } else {
                drawStatus = MallDrawStatus.PROCESSING;
            }
        }
        drawRecord.setDrawStatus(drawStatus.getCode());
        drawRecord.setParentId(parentId);
        drawRecord.setPoolIndex(prizePool.getPoolIndex());
        drawRecord.setPrizePoolId(prizePool.getId());
        drawRecord.setUserId(userInfo.getId());
        drawRecord.setPrizeId(prizeDetail != null ? prizeDetail.getId() : 0L);
        mallDrawRecordDao.save(drawRecord);

        // 未中奖则之前抽奖记录全部更新为未中奖且释放之前库存
        if (CollectionUtil.isNotEmpty(drawRecords) && !winPrize) {
            drawRecords.forEach(record -> {
                if (StrUtil.isNotBlank(record.getPrizeInfo())) {
                    MallDrawPrizeDTO drawPrizeDTO = JSON.parseObject(record.getPrizeInfo(), MallDrawPrizeDTO.class);
                    boolean result = mallDrawActivityPrizePoolDetailService.changePrizeCount(record.getPrizeId(),
                            drawPrizeDTO.getPrizeCount(), false);
                    record.setDrawStatus(MallDrawStatus.NOT_WIN.getCode());
                }
            });
            mallDrawRecordDao.updateBatchById(drawRecords, 50);
        }

        // 单轮奖池则自动领取奖品
        if (winPrize && singlePool) {
            sendPrize(userInfo.getId(), drawActivity, prizeDetail);
        }

        MallDrawPrizeResultVO result = new MallDrawPrizeResultVO();
        if (winPrize) {
            MallDrawPrizeVO prizeVO = new MallDrawPrizeVO();
            prizeVO.setDetailIndex(prizeDetail.getDetailIndex());
            prizeVO.setPrizeCount(prizeDetail.getPrizeCount());
            prizeVO.setPropImage(prizeDetail.getPropImg());
            prizeVO.setPropName(prizeDetail.getPropName());
            prizeVO.setPropType(prizeDetail.getPropType().getCode());
            result.setDrawPrize(prizeVO);

            pushPrizeNotice(prizeVO, userInfo.getUserTel(), activityId);
        }
        result.setDrawId(drawRecord.getId());
        // 中奖结果 0-未中奖 1-中奖 2-道具不足
        result.setDrawResult(winPrize ? 1 : 0);
        return result;
    }

    private void subVoucher(Long userId, Long propId, Integer count, Long actId) {
        userEquityPropsService.subVoucher(propId, null, userId, count, "商城抽奖消耗", actId,
                UserEquityPropsUseTypeEnum.EXCHANGE);
    }

    private void sendPrize(Long userId, MallDrawActivityBase drawActivity,
                           MallDrawActivityPrizePoolDetail prizeDetail) {
        if (PropTypeEnum.PROP.equals(prizeDetail.getPropType())) {
            // 发放道具
            EquityProps equityProps = equityPropsService.getById(prizeDetail.getPropId());
            userEquityPropsService.addEquityPropsByUser(equityProps, userId, UserEquityPropsGetTypeEnum.PRIZE_DRAW,
                    prizeDetail.getPrizeCount(), drawActivity.getName(), drawActivity.getId(), BigDecimal.ZERO,
                    UserEquityPropsGetTypeEnum.PRIZE_DRAW, drawActivity.getId());
        } else if (PropTypeEnum.INTEGRAL.equals(prizeDetail.getPropType())) {
            //   发放星球碎片
            userAssetsService.addIntegralByUserId(Convert.toBigDecimal(prizeDetail.getPrizeCount()), userId,
                    CoinCategoryEnum.INTEGRAL, AssetChangeLogEnum.LOTTERY, drawActivity.getId(), drawActivity.getName());
        }
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean acceptPrize(Long userId, Long activityId) {
        // 活动校验
        Date now = new Date();
        MallDrawActivityBase drawActivity = mallDrawActivityBaseService.getById(activityId);
        if (drawActivity == null) {
            throw new GlobalRunTimeException("活动不存在");
        } else if (drawActivity.getStartTime().after(now)) {
            throw new GlobalRunTimeException("活动暂未开始");
        } else if (drawActivity.getEndTime().before(now)) {
            throw new GlobalRunTimeException("活动已结束");
        }

        // 抽奖记录校验
        List<MallDrawRecord> drawRecords = mallDrawRecordDao.listByUserIdActIdAndStatus(userId, activityId,
                MallDrawStatus.PROCESSING);
        if (CollectionUtil.isEmpty(drawRecords)) {
            throw new GlobalRunTimeException("暂无待领取的奖品");
        }

        // 发放奖励
        List<Long> prizeIds = drawRecords.stream().map(MallDrawRecord::getPrizeId).collect(Collectors.toList());
        List<MallDrawActivityPrizePoolDetail> prizeList = mallDrawActivityPrizePoolDetailService.listByIds(prizeIds);
        for (MallDrawActivityPrizePoolDetail prizeDetail : prizeList) {
            sendPrize(userId, drawActivity, prizeDetail);
        }
        // 更新抽奖记录状态
        drawRecords.forEach(prizeDetail -> {
            prizeDetail.setDrawStatus(MallDrawStatus.ACCEPTED.getCode());
        });
        return mallDrawRecordDao.updateBatchById(drawRecords, 50);
    }

    @Override
    public MallUserPrizeVO getUserPrizePool(Long userId, Long activityId) {
        Date now = new Date();
        MallDrawActivityBase drawActivity = mallDrawActivityBaseService.getById(activityId);
        if (drawActivity == null || now.after(drawActivity.getEndTime())) {
            return null;
        }
        List<MallDrawRecord> drawRecords = mallDrawRecordDao.listByUserIdActIdAndStatus(userId, activityId,
                MallDrawStatus.PROCESSING);
        if (CollectionUtil.isEmpty(drawRecords)) {
            return null;
        }

        MallUserPrizeVO userPrizeVO = new MallUserPrizeVO();
        userPrizeVO.setActivityId(activityId);
        userPrizeVO.setDrawId(drawRecords.get(drawRecords.size() - 1).getId());
        List<MallDrawActivityPrizePool> prizePoolList = mallDrawActivityPrizePoolService.listByActivityId(activityId);
        Map<Long, MallDrawActivityPrizePool> prizePoolMap = prizePoolList.stream()
                .collect(Collectors.toMap(MallDrawActivityPrizePool::getId, Function.identity()));
        if (drawRecords.size() == prizePoolList.size()) {
            userPrizeVO.setContinueDraw(false);
        } else {
            MallDrawActivityPrizePool nextPrizePool = prizePoolList.get(drawRecords.size());
            userPrizeVO.setContinueDraw(true);
            userPrizeVO.setNextPoolIndex(nextPrizePool.getPoolIndex());
            userPrizeVO.setNextPrizePoolId(nextPrizePool.getId());
        }
        List<MallUserPrizeDetailVO> prizeList = new ArrayList<>();
        for (MallDrawRecord drawRecord : drawRecords) {
            MallUserPrizeDetailVO detailVO = new MallUserPrizeDetailVO();
            detailVO.setPoolIndex(drawRecord.getPoolIndex());
            detailVO.setPoolName(prizePoolMap.get(drawRecord.getPrizePoolId()).getPoolName());
            MallDrawPrizeDTO prizeDTO = JSON.parseObject(drawRecord.getPrizeInfo(), MallDrawPrizeDTO.class);
            detailVO.setPrizeCount(prizeDTO.getPrizeCount());
            detailVO.setPropImageUrl(prizeDTO.getPropImage());
            detailVO.setPropName(prizeDTO.getPropName());
            prizeList.add(detailVO);
        }
        userPrizeVO.setPrizeList(prizeList);
        return userPrizeVO;
    }

    @Override
    public MallDrawActivityVO getDrawActivity(Long userId, Long activityId) {
        Date now = new Date();
        MallDrawActivityBase drawActivity = mallDrawActivityBaseService.getById(activityId);
        if (drawActivity == null) {
            throw new GlobalRunTimeException("活动不存在");
        }
        MallDrawActivityVO activityVO = new MallDrawActivityVO();
        if (now.before(drawActivity.getStartTime())) {
            activityVO.setStatus(0);
        } else if (now.after(drawActivity.getEndTime())) {
            activityVO.setStatus(2);
        } else {
            activityVO.setStatus(1);
        }
        activityVO.setId(drawActivity.getId());
        activityVO.setCoverImageUrl(drawActivity.getCoverImageUrl());
        activityVO.setEndTime(drawActivity.getEndTime());
        activityVO.setName(drawActivity.getName());
        activityVO.setRuleImageUrl(drawActivity.getRuleImageUrl());
        activityVO.setStartTime(drawActivity.getStartTime());
        List<MallSimpleDrawPrizePoolVO> drawPrizePoolList = new ArrayList<MallSimpleDrawPrizePoolVO>();
        List<MallDrawActivityPrizePool> poolList = mallDrawActivityPrizePoolService.listByActivityId(activityId);
        Integer jumpPoolIndex = 1;
        List<MallDrawRecord> drawRecords = mallDrawRecordDao.listByUserIdActIdAndStatus(userId, activityId,
                MallDrawStatus.PROCESSING);
        if (CollectionUtil.isEmpty(drawRecords)) {
            jumpPoolIndex = 1;
            ;
        } else {
            if (drawRecords.size() == poolList.size()) {
                jumpPoolIndex = poolList.size();
            } else {
                jumpPoolIndex = poolList.get(drawRecords.size()).getPoolIndex();
            }
        }
        final Integer pooIndex = jumpPoolIndex;
        poolList.forEach(prizePool -> {
            MallSimpleDrawPrizePoolVO vo = new MallSimpleDrawPrizePoolVO();
            vo.setId(prizePool.getId());
            vo.setActivityId(prizePool.getActivityId());
            vo.setPoolIndex(prizePool.getPoolIndex());
            vo.setPoolName(prizePool.getPoolName());
            if (pooIndex == prizePool.getPoolIndex()) {
                vo.setJumpTo(true);
            } else {
                vo.setJumpTo(false);
            }
            drawPrizePoolList.add(vo);
        });
        activityVO.setDrawPrizePoolList(drawPrizePoolList);

        return activityVO;
    }

    @Override
    public MallDrawPrizePoolVO getPrizePool(Long userId, Long prizePoolId, Long activityId) {
        Date now = new Date();
        MallDrawActivityBase drawActivity = mallDrawActivityBaseService.getById(activityId);
        if (drawActivity == null) {
            throw new GlobalRunTimeException("活动不存在");
        }
        MallDrawActivityPrizePool prizePool = mallDrawActivityPrizePoolService.getById(prizePoolId);
        if (prizePool == null || !prizePool.getActivityId().equals(activityId)) {
            throw new GlobalRunTimeException("奖池不存在");
        }
        MallDrawPrizePoolVO poolVO = new MallDrawPrizePoolVO();
        poolVO.setActivityId(drawActivity.getId());
        poolVO.setDrawId(activityId);
        poolVO.setId(prizePool.getId());
        poolVO.setPoolIndex(prizePool.getPoolIndex());
        poolVO.setPoolName(prizePool.getPoolName());
        poolVO.setPoolStyle(prizePool.getPoolStyle());

        List<MallDrawActivityPrizePoolDetail> poolDetails = mallDrawActivityPrizePoolDetailService
                .listByPrizePoolId(prizePoolId);
        List<MallDrawPrizeVO> prizeList = new ArrayList<>();
        for (MallDrawActivityPrizePoolDetail detail : poolDetails) {
//            if (PropTypeEnum.NONE.equals(detail.getPropType())) {
//                continue;
//            }
            MallDrawPrizeVO vo = new MallDrawPrizeVO();
            vo.setDetailIndex(detail.getDetailIndex());
            vo.setPrizeCount(detail.getPrizeCount());
            vo.setPropImage(detail.getPropImg());
            vo.setPropName(detail.getPropName());
            vo.setPropType(detail.getPropType().getCode());
            prizeList.add(vo);
        }
        poolVO.setPrizeList(prizeList);

        List<MallSimplePropVO> useProps = new ArrayList<>();
        List<MallDrawActivityProp> actProps = mallDrawActivityPropService.listByActivityId(activityId);
        actProps.forEach(prop -> {
            MallSimplePropVO propVO = new MallSimplePropVO();
            propVO.setPropCount(prop.getPropCount());
            propVO.setPropId(prop.getPropId());
            propVO.setPropName(prop.getPropName());
            propVO.setPropImg(prop.getPropImg());
            useProps.add(propVO);
        });
        poolVO.setUseProps(useProps);

        List<Long> propIds = actProps.stream().map(MallDrawActivityProp::getPropId).collect(Collectors.toList());
        List<UserHoldPropsCountVO> propsHoldCountList = userEquityPropsService.getUserPropsHoldCount(userId, propIds);
        Map<Long, Integer> holdCountMap = propsHoldCountList.stream().collect(Collectors
                .toMap(UserHoldPropsCountVO::getEquityPropsId, UserHoldPropsCountVO::getHoldCount, (k1, k2) -> k1));
        int maxParticipations = Integer.MAX_VALUE;
        for (MallDrawActivityProp requiredProp : actProps) {
            Long propId = requiredProp.getPropId();
            Integer userHoldCount = holdCountMap.getOrDefault(propId, 0);
            if (userHoldCount == 0) {
                maxParticipations = 0;
                break;
            }
            Integer requiredCount = requiredProp.getPropCount();
            int possibleParticipations = userHoldCount / requiredCount;
            maxParticipations = Math.min(maxParticipations, possibleParticipations);
        }
        maxParticipations = maxParticipations == Integer.MAX_VALUE ? 0 : maxParticipations;
        poolVO.setDrawCount(maxParticipations);

        // 奖池状态 0-待开始 1-待抽奖 2-中奖可继续抽奖 3-中奖可领奖品 4-待解锁 5-已结束
        Integer count = mallDrawActivityPrizePoolService.countByActivityId(activityId);
        boolean lastPool = prizePool.getPoolIndex() == count;
        if (now.before(drawActivity.getStartTime())) {
            poolVO.setStatus(0);
        } else if (now.after(drawActivity.getEndTime())) {
            poolVO.setStatus(5);
        } else {
            List<MallDrawActivityPrizePool> poolList = mallDrawActivityPrizePoolService.listByActivityId(activityId);
            Integer totalPoolSize = poolList.size();
            MallDrawActivityPrizePool firstPool = poolList.get(0);
            List<MallDrawRecord> drawRecords = mallDrawRecordDao.listByUserIdActIdAndStatus(userId, activityId,
                    MallDrawStatus.PROCESSING);
            if (CollectionUtil.isEmpty(drawRecords)) {
                // 第一个
                if (prizePool.getId().equals(firstPool.getId()) || totalPoolSize == 1) {
                    poolVO.setStatus(1);
                } else {
                    poolVO.setStatus(4);
                }
            } else {
                MallDrawRecord lastRecord = drawRecords.get(drawRecords.size() - 1);
                if (lastRecord.getPoolIndex().equals(prizePool.getPoolIndex())) {
                    if (lastPool) {
                        poolVO.setStatus(3);
                    } else {
                        poolVO.setStatus(2);
                    }
                } else if (lastRecord.getPoolIndex() < prizePool.getPoolIndex()) {
                    if ((lastRecord.getPoolIndex() + 1) == prizePool.getPoolIndex()) {
                        poolVO.setStatus(1);
                    } else {
                        poolVO.setStatus(4);
                    }
                } else {
                    poolVO.setStatus(2);
                }
            }
        }
        poolVO.setLastPool(lastPool);
        return poolVO;
    }

    @Override
    public List<MallUserDrawRecordDetailVO> getUserDrawRecordDetail(Long id) {
        MallDrawRecord mallDrawRecord = mallDrawRecordDao.getById(id);
        return this.getUserDrawRecordDetail(mallDrawRecord.getUserId(), mallDrawRecord.getActivityId(), mallDrawRecord.getId());
    }

    @Override
    public List<MallUserDrawRecordDetailVO> getUserDrawRecordDetail(Long userId, Long activityId, Long drawId) {
        Date now = new Date();
        MallDrawActivityBase drawActivity = mallDrawActivityBaseService.getById(activityId);
        if (drawActivity == null) {
            throw new GlobalRunTimeException("活动不存在");
        }
        MallDrawRecord drawRecord = mallDrawRecordDao.getById(drawId);
        if (drawRecord == null) {
            throw new GlobalRunTimeException("抽奖记录不存在");
        }
        List<MallDrawRecord> recordList = mallDrawRecordDao.listByUserIdAndParentId(userId, activityId, drawId);
        recordList.add(0, drawRecord);
        MallDrawRecord lastRecord = recordList.get(recordList.size() - 1);
        List<MallUserDrawRecordDetailVO> result = new ArrayList<>();
        for (MallDrawRecord mallDrawRecord : recordList) {
            MallUserDrawRecordDetailVO detailVO = new MallUserDrawRecordDetailVO();
            detailVO.setCreateTime(mallDrawRecord.getCreateTime());
            detailVO.setTitle(getRecordTitle(mallDrawRecord));
            detailVO.setDesc(getDesc(mallDrawRecord, lastRecord.getId().equals(mallDrawRecord.getId())));
            result.add(detailVO);
        }

        // 添加过期信息
        if (drawActivity.getEndTime().before(now)
                && MallDrawStatus.PROCESSING.getCode() == drawRecord.getDrawStatus()) {
            MallUserDrawRecordDetailVO detailVO = new MallUserDrawRecordDetailVO();
            detailVO.setCreateTime(drawActivity.getEndTime());
            detailVO.setTitle("抽奖活动结束");
            detailVO.setDesc(Arrays.asList(EXPIRED_DESC));
            result.add(detailVO);
        }

        // 添加领奖信息
        if (MallDrawStatus.ACCEPTED.getCode() == drawRecord.getDrawStatus()) {
            MallUserDrawRecordDetailVO detailVO = new MallUserDrawRecordDetailVO();
            detailVO.setCreateTime(
                    drawRecord.getModifyTime() != null ? drawRecord.getModifyTime() : drawRecord.getCreateTime());
            detailVO.setTitle("用户领取奖品");
            List<String> descList = new ArrayList<>();
            for (MallDrawRecord record : recordList) {
                if (MallDrawStatus.PROCESSING.getCode() == record.getDrawStatus()
                        || MallDrawStatus.ACCEPTED.getCode() == record.getDrawStatus()) {
                    MallDrawPrizeDTO prizeDTO = JSON.parseObject(record.getPrizeInfo(), MallDrawPrizeDTO.class);
                    String desc = String.format(PRIZE_DESC, prizeDTO.getPropName(), prizeDTO.getPrizeCount());
                    descList.add(desc);
                }
            }
            detailVO.setDesc(descList);
            result.add(detailVO);
        }
        return result;
    }

    private List<String> getDesc(MallDrawRecord drawRecord, boolean isLastRecord) {
        Integer status = drawRecord.getDrawStatus();
        if (MallDrawStatus.PROCESSING.getCode() == status || MallDrawStatus.ACCEPTED.getCode() == status
                || (MallDrawStatus.NOT_WIN.getCode() == status && !isLastRecord)) {
            MallDrawPrizeDTO prizeDTO = JSON.parseObject(drawRecord.getPrizeInfo(), MallDrawPrizeDTO.class);
            String desc = String.format(WIN_FORMAT, prizeDTO.getPropName(), prizeDTO.getPrizeCount());
            return Arrays.asList(desc);
        } else if (MallDrawStatus.NOT_WIN.getCode() == status) {
            return Arrays.asList(NOT_WIN_DESC);
        }
        return Arrays.asList(EXPIRED_DESC);
    }

    private String getRecordTitle(MallDrawRecord drawRecord) {
        if (drawRecord.getPoolIndex() == 1) {
            return "首轮抽奖时间";
        }
        String index = Convert.numberToChinese(drawRecord.getPoolIndex(), false);
        return String.format(TITLE_FORMAT, index);
    }

    private void pushPrizeNotice(MallDrawPrizeVO prizeVO, String mobile, Long activityId) {
        try {
            String key = PRIZE_NOTICE_KEY + activityId;
            Long size = redisTemplate.opsForList().size(key);
            if (size != null && size > 5) {
                redisTemplate.opsForList().rightPop(key);
            }
            MallDrawPrizeNoticeVO vo = new MallDrawPrizeNoticeVO();
            vo.setMobile(DesensitizedUtil.mobilePhone(mobile));
            vo.setPropCount(prizeVO.getPrizeCount());
            vo.setPropName(prizeVO.getPropName());
            redisTemplate.opsForList().leftPush(key, JSON.toJSONString(vo));
            redisTemplate.expire(key, 5, TimeUnit.DAYS);
        } catch (Exception e) {
            log.error("[op:pushPrizeNotice] catch exception, e:", e);
        }
    }

    @Override
    public List<MallDrawPrizeNoticeVO> getDrawNotice(Long id) {
        String key = PRIZE_NOTICE_KEY + id;
        List<String> prizeList = redisTemplate.opsForList().range(key, 0, -1);
        if (prizeList == null) {
            return Collections.emptyList();
        }
        List<MallDrawPrizeNoticeVO> result = new ArrayList<>();
        prizeList.forEach(prize -> {
            result.add(JSON.parseObject(prize, MallDrawPrizeNoticeVO.class));
        });
        return result;
    }

}
